﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Threading;
using System.Net;
using System.Net.Sockets;
using System.IO;
using StressTesterCommon;
using System.Xml.Linq;
using System.ComponentModel;
using Microsoft.SqlServer;
using Microsoft.AnalysisServices.AdomdClient;



namespace StressServer
{
    
    public class StressServer:IDisposable
    {
        #region Event declarations
        public delegate void XMLACacheCleared(bool result);
        public event XMLACacheCleared OnXMLACacheCleared;
        public delegate void ClientDied(StressClientProxy Client);
        public event ClientDied OnClientDied;
        public delegate void ClientConnect(StressClientProxy Client);
        public event ClientConnect OnClientConnect;
        public delegate void ClientIdentified(StressClientProxy Client);
        public event ClientIdentified OnClientIdentified;
        public delegate void ClientMessageRecieved(Guid Id, TCPMessage Msg);
        public event ClientMessageRecieved MessageRecieved;
        public delegate void ClientQueryResultRecieved(StressClientProxy Client, QueryResult Result);
        public event ClientQueryResultRecieved QueryResultRecieved;
        public delegate void ClientDisconnect(StressClientProxy Client);
        public event ClientDisconnect OnClientDisconnect;
        public delegate void TestCompleted();
        public event TestCompleted OnTestCompleted;
        #endregion
        #region Members
        private object _LockQueryResults = new object();
        private object _LockClientCompletedTest = new object();
        private int _ClientCompletedTestCount = 0;
        private List<QueryResult> _QueryResults;
        public List<QueryResult> QueryResults
        {
            get { return _QueryResults; }
            set { _QueryResults = value; }
        }
        private bool _IsConnected = false;
        public bool IsConnected
        {
            get { return _IsConnected; }
            
        }
        public int TotalNumberOfQueries
        {
            get
            {
                int Count;
                Count = _Clients.Count * _TestConfiguration.TotalNumberOfQueries;
                return Count;
            }
        }
        private bool _AbortTest = false;
        private IPAddress _Ip;
        private TcpListener _ConnectionListener;
        private Thread _ListenerThread;
        private bool _DisconnectServer = false;
        private StreamWriter _LogWriter;
        private DateTime _TestStarted;
        private DateTime _TestEnded;
        private int _QueryCompletedCount = 0;       
        private int _ClientTimeout = 10;
        public int ClientTimeout
        {
            get { return _ClientTimeout; }
            set { _ClientTimeout = value; }
        }
        private int _HeartBeatInterval = 5;
        public int HeartBeatInterval
        {
            get { return _HeartBeatInterval; }
            set { _HeartBeatInterval = value; }
        }
        public DateTime TestStarted
        {
            get { return _TestStarted; }
            set { _TestStarted = value; }
        }
        private Test _TestConfiguration;
        public Test TestConfiguration
        {
            get { return _TestConfiguration; }
            set { _TestConfiguration = value; }
        }
        private List<StressClientProxy> _Clients = new List<StressClientProxy>();
        public List<StressClientProxy> Clients
        {
            get { return _Clients; }
            set { _Clients = value; }
        }
        #endregion
        #region Methods
        public void Dispose()
        {
            Dispose(true);
        }
        protected virtual void Dispose(bool dispose)
        {
            if (_ListenerThread != null)
            {
                if (_ListenerThread.IsAlive)
                {
                    _ListenerThread.Abort();
                }
                _ListenerThread = null;
            }

            if (_Clients != null)
            {
                for (int i = 0;i<_Clients.Count;i++) 
                {
                    _Clients[i].Disconnect();
                }
                _Clients.Clear();
                _Clients = null;
            }
        }
        public void AbortTest()
        {
            _AbortTest = true;
            foreach (StressClientProxy Cli in _Clients)
            {
                Cli.AbortTest();
            }
        }
        public int GetRemainingTestCount()
        {
            int ret = 0;
            foreach (StressClientProxy cli in _Clients)
            {
                ret += cli.QueryResults.Count;
            }
            ret = this.TestConfiguration.TotalNumberOfQueries - ret;
            return ret;
        }
        public StressClientProxy GetClientById(Guid Id)
        {
            foreach (StressClientProxy Cli in _Clients)
            {
                if (Cli.Id == Id)
                {
                    return Cli;
                }
            }
            return null;
        }
        public void Connect(int Port)
        {
            _DisconnectServer = false;
            _Ip = IPAddress.Parse("0.0.0.0");
     
            try
            {
                _ConnectionListener = new TcpListener(_Ip, Port);
                _ConnectionListener.Start();
            }
            catch (SocketException s)
            {
                throw;
            }
            _Clients = new List<StressClientProxy>();
            _ListenerThread = new Thread(new ThreadStart(ListenForConnections));
            _ListenerThread.SetApartmentState(ApartmentState.STA);
            _ListenerThread.Start();
            _IsConnected = true;
            
        }
        public bool ValidateTest()
        {
            if (_TestConfiguration == null)
            {
                return false;
            }

            if (_TestConfiguration.Connection == null)
            {
                return false;
            }

            if (_TestConfiguration.Connection.Validate() == false)
            {
                return false;
            }

            if (_TestConfiguration.OutputFileName == null || _TestConfiguration.OutputFileName == "")
            {
                return false;
            }
            return true;
          
        }
        public bool Disconnect()
        {
                    
            foreach (StressClientProxy Cli in _Clients)
            {
                Cli.Disconnect();
            }
            _Clients.Clear();
            _DisconnectServer = true;
            if(_ConnectionListener != null) 
            {
                _ConnectionListener.Stop();
                _ConnectionListener = null;
            }
            if (_ListenerThread != null)
            {         
                _ListenerThread.Abort();
                _ListenerThread = null;
            }
            _IsConnected = false;
            return true;
        }
        public void SendMessageToClients(string Message)
        {
            
            foreach (StressClientProxy Cli in _Clients)
            {
                Cli.SendMessage(Message);
            }
        }
        public void SendTestConfigToClients()
        {
            foreach (StressClientProxy Cli in _Clients)
            {
                Cli.SendTestConfiguration(_TestConfiguration);
            }
        }
        public void SendTestConnectionToClients()
        {
            foreach (StressClientProxy Cli in _Clients)
            {
                Cli.SendTestConnection();
            }
        }
        public StressServer()
        {
            _TestConfiguration = new Test();
            _IsConnected = false;

        }
        private void LogQueryResultToFile(QueryResult Res)
        {
            StressClientProxy Cli = GetClientById(Res.ClientId);
            string Row = this.TestConfiguration.Name + TestConfiguration.LogDelimiter +
                         this.TestStarted.ToString() + TestConfiguration.LogDelimiter +
                         Cli.IP + TestConfiguration.LogDelimiter +
                         Cli.DomainName + TestConfiguration.LogDelimiter +
                         Cli.MachineName + TestConfiguration.LogDelimiter +
                         Cli.UserName + TestConfiguration.LogDelimiter +
                         Res.QueryName + TestConfiguration.LogDelimiter +
                         System.Text.RegularExpressions.Regex.Replace(Res.QueryText, @"\s{2,}", " ") + TestConfiguration.LogDelimiter +
                         Res.ExecuteStatus.ToString() + TestConfiguration.LogDelimiter +
                         Res.Duration.ToString() + TestConfiguration.LogDelimiter +
                         Res.QueryStartTime + TestConfiguration.LogDelimiter + 
                         Res.QueryEndTime + TestConfiguration.LogDelimiter + 
                         Res.ErrorId + TestConfiguration.LogDelimiter + 
                         Res.ErrorMessage;
            
            try
            {
                _LogWriter.WriteLine(Row);
            }
            catch (Exception)
            {
                throw;
            }
           
        }
        private void PrepareLogFile()
        {
            bool WriteHeader = false;
            string Line = "Test Name" + TestConfiguration.LogDelimiter + "Test Started" + TestConfiguration.LogDelimiter + "Client IP" + TestConfiguration.LogDelimiter + "Client Domain" + TestConfiguration.LogDelimiter + "Client Machine" + TestConfiguration.LogDelimiter + "Client User" + TestConfiguration.LogDelimiter + "Query Name" + TestConfiguration.LogDelimiter + "Query Text" + TestConfiguration.LogDelimiter + "Query Execution Status" + TestConfiguration.LogDelimiter + "Query Duration" + TestConfiguration.LogDelimiter + "Start Time" + TestConfiguration.LogDelimiter + "End Time" + TestConfiguration.LogDelimiter + "Error Id" + TestConfiguration.LogDelimiter + "Error Message";
            if (File.Exists(TestConfiguration.OutputPath))
            {
                StreamReader LogReader = new StreamReader(TestConfiguration.OutputPath);
                string s = LogReader.ReadLine();
                LogReader.Close();
                LogReader = null;
                if (s == Line)
                {
                    WriteHeader = false;
                }
                else
                {
                    WriteHeader = true;
                }
            }
            if (WriteHeader | !TestConfiguration.AppendToOutput)
            {
                _LogWriter = new StreamWriter(TestConfiguration.OutputPath, false);
                _LogWriter.WriteLine(Line);
            }
            else
            {
                _LogWriter = new StreamWriter(TestConfiguration.OutputPath, true);
            }
            
            
        }
        public void SendExecuteTestToClients()
        {
            _TestStarted = DateTime.Now;
            _QueryResults = new List<QueryResult>();
            _AbortTest = false;
            //Pre test stuff
            if (TestConfiguration.DoClearCache)
            {
                bool result = TestConfiguration.ClearCache();

                OnXMLACacheCleared(result);
                if (!result)
                {
                    _AbortTest = true;
                    return;
                }
            }
            _AbortTest = false;
            if (_TestConfiguration == null)
            {
                return;
            }
            
            _QueryCompletedCount = 0;
            foreach (StressClientProxy Cli in _Clients)
            {
                Cli.SendExecuteTest();
            }
        }
        public void ListenForConnections()
        {
            while (!_DisconnectServer)
            {
                for (int i = 0; i < _Clients.Count;i++ )
                {
                    StressClientProxy Client = _Clients[i];
                    if (Client.SecondsSinceLastContact > _ClientTimeout)
                    {
                        //Evict client
                        if (OnClientDied != null) { OnClientDied(Client); }
                        Client.Disconnect();
                        _Clients.Remove(Client);
                    }
                }

                if (_ConnectionListener.Pending())
                {
                    try
                    {
                        StressClientProxy NewClient = new StressClientProxy();
                        NewClient.Parent = this;
                        NewClient.Client = _ConnectionListener.AcceptTcpClient();
                        NewClient.Listen();
                        //Request client data
                        byte[] msg = TCPMessage.FormatMessage(MessageTypes.ClientProvideIdData, "");
                        Stream s = NewClient.Client.GetStream();
                        s.Write(msg, 0, msg.Length);
                        //Signal connect
                        if (OnClientConnect != null) { OnClientConnect(NewClient); }
                        
                        //Add event handlers
                        NewClient.OnIdentified += NewClient_OnIdentified;
                        NewClient.MessageRecieved += NewClient_MessageRecieved;
                        NewClient.QueryResultRecieved += NewClient_QueryResultRecieved;
                        NewClient.OnDisconnect += NewClient_OnDisconnect;
                        NewClient.OnQueryResultSetRecieved += NewClient_OnQueryResultSetRecieved;
                        NewClient.OnClientTestCompleted += NewClient_OnClientTestCompleted;
                        _Clients.Add(NewClient);
                        
                    }

                    catch (SocketException e)
                    {
                        throw;
                    }

                }
                Thread.Sleep(10);
            }
        }
        public void WriteResultToFile()
        {
            if (!_AbortTest)
            {

                try
                {
                    PrepareLogFile();
                    foreach (StressClientProxy Client in _Clients)
                    {
                        foreach (QueryResult Result in Client.QueryResults)
                        {
                            LogQueryResultToFile(Result);
                        }
                    }
                    _LogWriter.Flush();
                    _LogWriter.Close();
                    _LogWriter = null;
                }
                catch
                {
                    if (_LogWriter != null)
                    {
                        _LogWriter.Flush();
                        _LogWriter.Close();
                        _LogWriter = null;
                    }
                    throw;
                }
            }


        }
        public void WriteReport(string Path)
        {
            TestResult Result = new TestResult();
            Result.GenerateReport(Path, _Clients, _TestConfiguration, this.TestStarted, this._TestEnded);
            
        }
        #endregion
        #region Events
        void NewClient_OnClientTestCompleted(StressClientProxy Client)
        {
            lock (_LockClientCompletedTest)
            {
                _ClientCompletedTestCount++;
                if (_ClientCompletedTestCount >= _Clients.Count)
                {
                    _TestEnded = DateTime.Now;
                    if (OnTestCompleted != null)
                    {
                        OnTestCompleted();
                    }
                }
            }
        }
        void NewClient_OnIdentified(StressClientProxy Client)
        {
            //rethrow
            if (OnClientIdentified != null) { OnClientIdentified(Client); }
        }
        void NewClient_OnDisconnect(StressClientProxy Client)
        {
            try
            {
                if (OnClientDisconnect != null) { OnClientDisconnect(Client); }

                _Clients.Remove(Client);
                Client.Disconnect();

                Client = null;
            }
            catch
            {
            }

        }
        void NewClient_QueryResultRecieved(StressClientProxy Client, QueryResult Result)
        {
            lock (this) 
            if (!_AbortTest)
            {
                Result.ClientId = Client.Id;
                _QueryResults.Add(Result);
                //Rethrow
                if (QueryResultRecieved != null)
                {
                    QueryResultRecieved(Client, Result);
                }
                //If test is completed finalize it
                _QueryCompletedCount++;
                //if (_QueryCompletedCount == (_TestConfiguration.TotalNumberOfQueries * _Clients.Count)) FinalizeTest();
            }
        }
        void NewClient_OnQueryResultSetRecieved(StressClientProxy Client, int Count)
        {

        }
        void NewClient_MessageRecieved(Guid Id, TCPMessage Msg)
        {
            //Rethrow
            if (MessageRecieved != null) { MessageRecieved(Id, Msg); }
        }
        #endregion
    }

   

}
